import time
import os
import inspect
import logging
from functools import wraps
from typing import Callable, Any, Optional
import threading
from logging.handlers import TimedRotatingFileHandler
from app.config.settings import settings

_timing_logger_lock = threading.Lock()
_timing_logger_cache = {}  # 每个进程独立缓存


def _get_timing_logger() -> logging.Logger:
    """
    获取当前进程专属的 timing logger。
    每个进程使用独立的日志文件（含 PID），确保多进程安全。
    """
    pid = os.getpid()
    if pid in _timing_logger_cache:
        return _timing_logger_cache[pid]

    with _timing_logger_lock:
        if pid in _timing_logger_cache:
            return _timing_logger_cache[pid]

        log_dir = "../logs"
        os.makedirs(log_dir, exist_ok=True)
        # 检查是否为调试模式
        is_debug = settings.get('app.dev', False)
        if is_debug:
            log_file = os.path.join(log_dir, f"timing.log") # 用于开发测试
        else:
            log_file = os.path.join(log_dir, f"timing_{pid}.log")  # 用于生产环境



        logger_name = f"timing_{pid}"
        logger = logging.getLogger(logger_name)
        logger.setLevel(logging.DEBUG)
        logger.propagate = False

        if not logger.handlers:
            # 控制台输出（始终开启，便于调试）
            console_handler = logging.StreamHandler()
            console_formatter = logging.Formatter(
                "%(asctime)s - %(levelname)s - [%(name)s] - %(message)s"
            )
            console_handler.setFormatter(console_formatter)
            logger.addHandler(console_handler)

            # 文件输出：按天轮转，保留7天
            file_handler = TimedRotatingFileHandler(
                filename=log_file,
                when="midnight",
                interval=1,
                backupCount=7,
                encoding="utf-8",
                utc=False,
            )
            file_formatter = logging.Formatter(
                "%(asctime)s - %(levelname)s - [%(name)s] - %(message)s"
            )
            file_handler.setFormatter(file_formatter)
            logger.addHandler(file_handler)

        _timing_logger_cache[pid] = logger
        return logger


def timing_decorator(
    logger: Optional[logging.Logger] = None,
    log_level: int = logging.INFO,
    error_log_level: int = logging.ERROR,
    include_caller: bool = True,
    prefix: str = "",
):
    """
    同步函数耗时装饰器。

    :param logger: 自定义 logger，若为 None 则使用内置 timing logger
    :param log_level: 成功日志级别，默认 INFO
    :param error_log_level: 异常日志级别，默认 ERROR
    :param include_caller: 是否记录调用者位置（文件:行号）
    :param prefix: 用户自定义前缀字符串，会添加在默认消息前，支持格式化字符串，
                  可使用函数参数名作为占位符，例如 f"创建{model_name}示例"
    """
    actual_logger = logger or _get_timing_logger()

    def decorator(func: Callable) -> Callable:
        original_func = func.__func__ if isinstance(func, (staticmethod, classmethod)) else func
        sig = inspect.signature(original_func)

        @wraps(original_func)
        def wrapper(*args, **kwargs) -> Any:
            caller_info = _find_caller_outside_decorator() if include_caller else None
            start = time.perf_counter()
            
            # 绑定实际参数到形参名称
            bound_args = sig.bind(*args, **kwargs)
            bound_args.apply_defaults()
            
            # 格式化 prefix 字符串
            formatted_prefix = prefix.format(**bound_args.arguments) if prefix else ""
            
            try:
                result = original_func(*args, **kwargs)
                elapsed = time.perf_counter() - start
                _log_timing_success(actual_logger, original_func, elapsed, caller_info, log_level, formatted_prefix)
                return result
            except Exception as e:
                elapsed = time.perf_counter() - start
                _log_timing_error(actual_logger, original_func, elapsed, e, caller_info, error_log_level, formatted_prefix)
                raise

        if isinstance(func, staticmethod):
            return staticmethod(wrapper)
        elif isinstance(func, classmethod):
            return classmethod(wrapper)
        return wrapper

    return decorator


def async_timing_decorator(
    logger: Optional[logging.Logger] = None,
    log_level: int = logging.INFO,
    error_log_level: int = logging.ERROR,
    include_caller: bool = True,
    prefix: str = "",
):
    """
    异步函数耗时装饰器。

    参数同 timing_decorator。
    """
    actual_logger = logger or _get_timing_logger()

    def decorator(func: Callable) -> Callable:
        original_func = func.__func__ if isinstance(func, (staticmethod, classmethod)) else func
        sig = inspect.signature(original_func)

        @wraps(original_func)
        async def async_wrapper(*args, **kwargs) -> Any:
            caller_info = _find_caller_outside_decorator() if include_caller else None
            start = time.perf_counter()
            
            # 绑定实际参数到形参名称
            bound_args = sig.bind(*args, **kwargs)
            bound_args.apply_defaults()
            
            # 格式化 prefix 字符串
            formatted_prefix = prefix.format(**bound_args.arguments) if prefix else ""
            
            try:
                result = await original_func(*args, **kwargs)
                elapsed = time.perf_counter() - start
                _log_timing_success(actual_logger, original_func, elapsed, caller_info, log_level, formatted_prefix)
                return result
            except Exception as e:
                elapsed = time.perf_counter() - start
                _log_timing_error(actual_logger, original_func, elapsed, e, caller_info, error_log_level, formatted_prefix)
                raise

        if isinstance(func, staticmethod):
            return staticmethod(async_wrapper)
        elif isinstance(func, classmethod):
            return classmethod(async_wrapper)
        return async_wrapper

    return decorator


def _find_caller_outside_decorator() -> Optional[inspect.FrameInfo]:
    """
    查找第一个非当前模块（timing.py）的调用者。
    """
    frame = inspect.currentframe()
    try:
        if frame is not None:
            frame = frame.f_back  # 进入 wrapper
        current_file = os.path.abspath(__file__)

        while frame is not None:
            filename = os.path.abspath(frame.f_code.co_filename)
            basename = os.path.basename(filename).lower()
            if (
                filename != current_file
                and "site-packages" not in filename
                and "python" not in basename
                and not basename.startswith("logging")
            ):
                return inspect.FrameInfo(
                    frame=frame,
                    filename=frame.f_code.co_filename,
                    lineno=frame.f_lineno,
                    function=frame.f_code.co_name,
                    code_context=None,
                    index=None,
                )
            frame = frame.f_back
        return None
    finally:
        del frame


def _log_timing_success(logger: logging.Logger, func, elapsed_sec: float, caller_info, log_level: int, prefix: str = ""):
    if logger.isEnabledFor(log_level):
        func_name = func.__name__
        prefix_str = f"{prefix} " if prefix else ""
        if caller_info:
            msg = f"{os.path.basename(caller_info.filename)}:{caller_info.lineno} - {prefix_str}{func_name} completed in {elapsed_sec:.3f}s"
        else:
            msg = f"{prefix_str}{func_name} completed in {elapsed_sec:.3f}s"
        logger.log(log_level, msg)


def _log_timing_error(logger: logging.Logger, func, elapsed_sec: float, exc: Exception, caller_info, error_log_level: int, prefix: str = ""):
    if logger.isEnabledFor(error_log_level):
        func_name = func.__name__
        prefix_str = f"{prefix} " if prefix else ""
        if caller_info:
            msg = f"{os.path.basename(caller_info.filename)}:{caller_info.lineno} - {prefix_str}{func_name} failed in {elapsed_sec:.3f}s, error: {exc}"
        else:
            msg = f"{prefix_str}{func_name} failed in {elapsed_sec:.3f}s, error: {exc}"
        logger.log(error_log_level, msg)